#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <math.h>
#include <sys/time.h>
#include <sys/select.h>
#include <pthread.h>

#include "socks.h"

//define global consts
#define LATENCY_CONV_SIZE 10
#define ARR_SIZE 15
#define MAXDATASIZE 1024
#define LOGN2 0.693147181
#define MAX_NUM_THREADS 10


typedef struct latency_timings_tag{
   struct timeval init_time;
   float  open;
   float  socks_open;
   float  conv[10]; 
   struct timeval last_time;

}Latency_Timings;


typedef struct bw_timings_tag{
   uint32_t user_bytes;
   float total_time;
   int intra_delay[ARR_SIZE];
   struct timeval init_time;
}BW_Timings;

typedef struct thread_info_tag{
   int thread_id;
   int host_id;
}thread_info_t;

//now define global variables
char *default_host="localhost";
char *SOCKS_HOST=NULL;
//char *SOCKS_HOST="10.4.0.1";
//#define SOCKS_PORT 9050
//#define SOCKS_PORT 1080
uint16_t socks_port=9050;

Latency_Timings thread_latencies[MAX_NUM_THREADS];
BW_Timings      thread_bw_timings[MAX_NUM_THREADS];
int do_load=0;

char *ftp_names[] ={
      //"localhost",
      "ftp.ussg.iu.edu",
      "ftp.redhat.com", "sunsite.informatik.rwth-aachen.de",
      "ftp.uni-erlangen.de", "ftp.jaist.ac.jp",
      "ftp.tcc.edu.tw"
};

char *http_url[] = {
       "http://ftp.kaist.ac.kr/pub/opensuse/distribution/10.2/iso/dvd/openSUSE-10.2-GM-DVD-i386.iso"
};

char *http_host[] ={
      "www.cs.indiana.edu",
      "chuck.ucs.indiana.edu",
      "www.honeynet.org",
      "honeynet.fh.net",
      "www.honeynet.nl"
};

char *http_file_path[]={
     "/~cviecco/personal/Fedora-8-x86_64-rescuecd.iso",
     "/linux/fedora/linux/releases/8/Fedora/x86_64/iso/Fedora-8-x86_64-rescuecd.iso",
     "/tools/cdrom/roo/iso/current/roo-1.2.hw-1.iso",
     "/tools/cdrom/roo/iso/current/roo-1.2.hw-1.iso",
     "/tools/cdrom/roo/iso/current/roo-1.2.hw-1.iso"
};



char *ftp_conv[] ={
      "USER anonymous\r\n", "PASS anon@anon.com\r\n",
      "SYS\r\n", 
      "PASV\r\n",
      //"LIST\r\n",
      "QUIT\r\n"
};

///------------

///-----------------------Now funcitons
inline float log2f(float in){return logf(in)/LOGN2;};


float tdiff(struct timeval *start,struct timeval *end){
    long int secs,usecs;
    secs=end->tv_sec-start->tv_sec;
    usecs=end->tv_usec-start->tv_usec;
    if(usecs<0){
       secs=secs-1;
       usecs=(usecs+1000000)%1000000;
    }
    return secs+usecs/1000000.0;
};

int print_timings(FILE *filestream,Latency_Timings *in){
    int i;

    fprintf(filestream,"%d.%06d : ",(int)in->init_time.tv_sec,(int)in->init_time.tv_usec);
    fprintf(filestream,"%f %f : ",in->open,in->socks_open);
    for (i=0;i<6;i++){
       fprintf(filestream,"%f ",in->conv[i]);
    }
    fprintf(filestream,": %d.%d ",(int)in->last_time.tv_sec,(int)in->last_time.tv_usec);


}

int print_bw_timings(FILE *filestream,BW_Timings *in){
    int i;

    fprintf(filestream,"%d.%06d : ",(int) in->init_time.tv_sec,(int) in->init_time.tv_usec);
    fprintf(filestream,"%8u %f ", in->user_bytes,in->total_time);
    for (i=0;i<ARR_SIZE;i++){
       fprintf(filestream,"%4u ",in->intra_delay[i]);
    }

}


int timeout_recv(int socket, void *buffer, size_t length, int flags, struct timeval *timeout){

     fd_set read_set;
     int rvalue;
     FD_ZERO(&read_set);
     FD_SET(socket,&read_set);
     do{
     rvalue=select(socket+1, &read_set,
              NULL, NULL, timeout);
     }while(rvalue<=0 && errno==EINTR);
     if (FD_ISSET(socket, &read_set)){
        return recv(socket,buffer,length,flags);
     }
     else
        return -1;

}

int host_connect(char *hostname,uint16_t port,Latency_Timings *measure){
    struct hostent *he;
    struct sockaddr_in their_addr;
    int fd;
    struct timeval tstor[2];

    //---prepare socket
    if ((he=gethostbyname(hostname)) == NULL) {  // get the host info
        herror("gethostbyname");
        return -1;
     }
    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        return -1;
    }
    their_addr.sin_family = AF_INET;    // host byte order
    their_addr.sin_port = htons(port);  // short, network byte order
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
    //--------connect to socks_server
    gettimeofday(&tstor[0], NULL);
    if (connect(fd, (struct sockaddr *)&their_addr,
                                          sizeof their_addr) == -1) {
        perror("connect");
        return -1;
    }
    gettimeofday(&tstor[1], NULL);
    measure->open=tdiff(&tstor[0],&tstor[1]);

    return fd;
}


int socks4a_connect(char *hostname,int port,Latency_Timings *measure){
    //returns an open fd and fills basic timing information on success
    // on error returns a negative number, on success a fd number 
    int fd;
    struct timeval tstor[2];
    char buf[MAXDATASIZE];
    struct hostent *he;
    struct sockaddr_in their_addr; 
    int hostlen;
    Socks_Header *request,*reply;
    int numbytes;
    char *name_ptr;
    char *user_ptr;

    //---prepare socket
    if ((he=gethostbyname(SOCKS_HOST)) == NULL) {  // get the host info
        herror("gethostbyname");
        return -1;
     }
    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        return -1;
    }
    their_addr.sin_family = AF_INET;    // host byte order
    their_addr.sin_port = htons(socks_port);  // short, network byte order
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);

    //--------connect to socks_server
    gettimeofday(&tstor[0], NULL);
    if (connect(fd, (struct sockaddr *)&their_addr,
                                          sizeof their_addr) == -1) {
        perror("connect");
        return -1;
    }
    gettimeofday(&tstor[1], NULL);
    measure->open=tdiff(&tstor[0],&tstor[1]);

    //---------prepare socks_request...
    hostlen=strnlen(hostname,MAXDATASIZE-sizeof(Socks_Header)-3);
    if(hostlen>=MAXDATASIZE-sizeof(Socks_Header)-3){
        fprintf(stderr,"too long of a hostname, aborting\n");
        close(fd);
        return -1;
    }
    
    memset(buf,0x00,MAXDATASIZE);
    request=(Socks_Header *)buf;
    request->version=4;
    request->command=1;
    request->dest=htonl(12);
    request->port=htons(port);
    
    buf[sizeof(Socks_Header)]='x';
    

    name_ptr=buf+sizeof(Socks_Header)+2;
    strncpy(name_ptr,hostname,hostlen);

    //-------send request....
    gettimeofday(&tstor[0], NULL);
    numbytes=hostlen+sizeof(Socks_Header)+3;
    if ((numbytes=send(fd, buf, numbytes, 0)) != numbytes) {
        perror("send");
        close(fd);
        return -1;
    }
    

    //get response
    if ((numbytes=recv(fd, buf, sizeof(Socks_Header), 0)) <= 0) {
        perror("recv");
        close(fd);
        return -1;
    }
    gettimeofday(&tstor[1], NULL);
    measure->socks_open=tdiff(&tstor[0],&tstor[1]);
    measure->last_time=tstor[1];

    //--interpret the response
     reply=(Socks_Header *)buf;
    //check the response is OK
    switch(reply->command){
       case 0x5a: break; //OK response
       case 0x5b: fprintf(stderr,"socks response denied");
                  return -1;
                  break;
       default:   fprintf(stderr,"Unkown error");
                  close(fd);
                  return -1;
                  break;

    }
    
    //we are done!!!
    return fd;

}

int ftp_talk(int fd, Latency_Timings *measure){
    int i;
    char buf[MAXDATASIZE];
    int numbytes;
    struct timeval tstor[2];

    //wait for hello
    tstor[0].tv_sec=12;
    tstor[0].tv_usec=0;
    if ((numbytes=timeout_recv(fd, buf, MAXDATASIZE, 0,&tstor[0])) <=0 ) {
       perror("recv");
       fprintf(stderr,"numbytes=%d",numbytes);
       return -1;
    }
    gettimeofday(&tstor[1], NULL);
    measure->conv[0]=tdiff(&measure->last_time,&tstor[1]);
    //fprintf(stderr,"ftp got hello!");


    for (i=0;i<5;i++){
       //send, wait for reply
       numbytes=strnlen(ftp_conv[i],MAXDATASIZE-1);

       gettimeofday(&tstor[0], NULL);

       if ((numbytes=send(fd, ftp_conv[i], numbytes, 0)) <= 0) {
          perror("ftp conv send");
          return -1;
       }
       //get response
       if ((numbytes=recv(fd, buf, MAXDATASIZE, 0)) <=0) {
          perror("recv");
          return -1;
        } 
       //we are so lazy, wont interpret the response...
       gettimeofday(&tstor[1], NULL);
       measure->conv[i+1]=tdiff(&tstor[0],&tstor[1]);
       measure->last_time=tstor[1];


    }

}

//-----------------
int http_talk(int fd,BW_Timings *measure, char *hostname, char *fileandpath){
    char buf[MAXDATASIZE];
    int numbytes;
    struct timeval tstor[2];
    struct timeval start_time;
    char getbuf[1024];
    int selector;    
 
    
    gettimeofday(&measure->init_time, NULL);
    //Prepare request    
    snprintf(buf,MAXDATASIZE-1,"GET %s HTTP/1.1\r\nHost: %s\r\n\r\n",fileandpath,hostname);
    numbytes=strnlen(buf,MAXDATASIZE-1);
    //send_request..
    if ((numbytes=send(fd, buf, numbytes, 0)) <= 0) {
        perror("ftp conv send");
        return -1;
    }
    //fprintf(stderr,"req:\n%s\n",buf);
    gettimeofday(&start_time,NULL);
    measure->user_bytes=0;
    //now iterate over the response
    do{
        gettimeofday(&tstor[0], NULL);
        
        if ((numbytes=recv(fd, buf, MAXDATASIZE, 0)) <=0) {
          perror("recv");
          return -1;
        }
        gettimeofday(&tstor[1], NULL);
  
        selector=(log2f( (int) (tdiff(&tstor[0],&tstor[1])*1000)));
        selector=selector & 0xFFFF; //for 64 bit environments!
        measure->user_bytes+=numbytes;
        measure->intra_delay[selector]++;
        measure->total_time=tdiff(&start_time,&tstor[1]);
        buf[numbytes]=0;
        //fprintf(stderr,"buf:\n%s\n",buf);
        //fprintf(stderr,"numbytes=%d\n",numbytes);
        if(do_load!=0){
            //calculate sleep nanosecs...        
        } 
    }while(measure->user_bytes<50*1024*1024 && tdiff(&measure->init_time,&tstor[1])<30  );
    
    return 0;
}


//------------
int do_socks_ftp(char *name,Latency_Timings *measure){
    int sock_fd;
    int rvalue;

    gettimeofday(&measure->init_time, NULL);
    sock_fd=socks4a_connect(name,21,measure);
    if(sock_fd<0){
       fprintf(stderr,"Error on socks4a connect\n");
       return -1;
    }
    //fprintf(stdout,"ftp:connected\n");
    rvalue=ftp_talk(sock_fd,measure);
    close(sock_fd);
    if(rvalue<0){
       fprintf(stderr,"Error on socks4a connect\n");
       return -1;
    }


    return 0;

}

//------
int do_socks_http(char *host, char *filepath, BW_Timings *measure){
    int sock_fd;
    int rvalue;
    Latency_Timings lat_measure;

    sock_fd=socks4a_connect(host,80,&lat_measure);
    if(sock_fd<0){
       fprintf(stderr,"Error on socks4a connect\n");
       return -1;
    }
    //fprintf(stdout,"http: connected\n");
    rvalue=http_talk(sock_fd,measure,host,filepath);
    close(sock_fd);
    if(rvalue<0){
       fprintf(stderr,"Error on http talk\n");
       return -1;
    }
    return 0;
}

int do_http(char *host, char *filepath, BW_Timings *measure){
    int fd;
    int rvalue;
    Latency_Timings lat_measure;

    fd=host_connect(host,80,&lat_measure);
    if(fd<0){
       fprintf(stderr,"Error on host connect\n");
       return -1;
    }
    //fprintf(stdout,"http: connected\n");
    rvalue=http_talk(fd,measure,host,filepath);
    close(fd);
    if(rvalue<0){
       fprintf(stderr,"Error on http talk\n");
       return -1;
    }

    return 0;
}

//-------------------------------------
void *thread_do_socks_http(void *in){
    //int thread_num=(int )in;
    thread_info_t *in_info=(thread_info_t *)in;
    int rvalue;
    int i=in_info->host_id;
    int j=in_info->thread_id;
    //do_socks_http(char *host, char *filepath, BW_Timings *measure)
    rvalue=do_socks_http(http_host[i], http_file_path[i], thread_bw_timings+j);
    //fprintf(stderr,"thead[%d] done with %d,rvalue=%d\n",j,i,rvalue);
}

void *thread_do_socks_ftp(void *in){
    //int thread_num=(int )in;
    thread_info_t *in_info=(thread_info_t *)in;
    int rvalue;
    int i=in_info->host_id;
    int j=in_info->thread_id;
    //do_socks_http(char *host, char *filepath, BW_Timings *measure)
    //do_socks_ftp(char *name,Latency_Timings *measure)
    rvalue=do_socks_ftp(ftp_names[i], thread_latencies+j);
    //fprintf(stderr,"thead[%d] done with %d,rvalue=%d\n",j,i,rvalue);
}


//----------------------
int do_all_socks_ftp_and_print(){
    Latency_Timings measure;
    int i;
    int rvalue;
    FILE *filestream=stdout;

    for(i=0;i<3;i++){
       memset(&measure,0x00,sizeof(Latency_Timings));

       rvalue=do_socks_ftp(ftp_names[i],&measure);
       if(rvalue>=0){
         print_timings(filestream,&measure);
         fprintf(filestream,"\n");
         fflush(filestream);
       }
       else{
         fprintf(stderr,"error with host %s \n",ftp_names[i]);
       }
    } 
    return 0;

}
int do_all_socks_http_and_print(){
    BW_Timings local1;
    int i;
    int rvalue;
    FILE *filestream=stdout;

    for (i=0;i<3;i++){
       memset(&local1,0x00,sizeof(BW_Timings));
       rvalue=do_socks_http(http_host[i], http_file_path[i], &local1);
       if (rvalue>=0){
         print_bw_timings(filestream,&local1);
         fprintf(filestream,"\n");
       }
       else{
         fprintf(stderr,"error on http with host %s\n",http_host[i]);
       }

    }
}
  

int mt_do_all_socks_http_and_print(int num_threads){
    pthread_t thread[MAX_NUM_THREADS];
    BW_Timings local1;
    int i,j;
    int rvalue;
    FILE *filestream=stdout;
    //int num_threads=6;
    int num_hosts=1;
    struct timespec delay;
    thread_info_t thread_info[MAX_NUM_THREADS];

    //make all threads,
    //wait for all threds
    //print all outputs
    for(i=0;i<num_hosts;i++){
       //make all threads
       memset(thread_bw_timings,0x00,sizeof(BW_Timings)*MAX_NUM_THREADS);
       for(j=0;j<num_threads;j++){
          thread_info[j].host_id=i;
          thread_info[j].thread_id=j;
          rvalue=pthread_create(&thread[j],NULL,thread_do_socks_http,&thread_info[j]);
          if(0!=rvalue){
             perror("cannot create thread"); exit(1);
          }

       //wait 7 seconds
       delay.tv_sec=2;
       delay.tv_nsec=000000000;
       //nanosleep(&delay,NULL);


       }
       //now join all threads
       for(j=0;j<num_threads;j++){
          rvalue=pthread_join(thread[j],NULL);
          if(0!=rvalue){
             perror("cannot join thread"); exit(1);
          }
       }
       //now print the results
       for(j=0;j<num_threads;j++){
          fprintf(filestream,"thread[%d] ",j);
          print_bw_timings(filestream,&thread_bw_timings[j]);
          fprintf(filestream,"\n");
       }

    }

}

int mt_do_all_socks_ftp_and_print(int num_threads){

    pthread_t thread[MAX_NUM_THREADS];
    int i,j;
    int rvalue;
    FILE *filestream=stdout;
    //int num_threads=6;
    int num_hosts=1;
    thread_info_t thread_info[MAX_NUM_THREADS];

    //make all threads,
    //wait for all threds
    //print all outputs
    for(i=0;i<num_hosts;i++){
       //make all threads
       memset(thread_latencies,0x00,sizeof(Latency_Timings)*MAX_NUM_THREADS);
       for(j=0;j<num_threads;j++){
          thread_info[j].host_id=i;
          thread_info[j].thread_id=j;
          rvalue=pthread_create(&thread[j],NULL,thread_do_socks_ftp,&thread_info[j]);
          if(0!=rvalue){
             perror("cannot create thread"); exit(1);
          }
       }
       //now join all threads
       for(j=0;j<num_threads;j++){
          rvalue=pthread_join(thread[j],NULL);
          if(0!=rvalue){
             perror("cannot join thread"); exit(1);
          }
       }
       //now print the results
       for(j=0;j<num_threads;j++){
          fprintf(filestream,"thread[%d] ",j);
          //print_bw_timings(filestream,&thread_bw_timings[j]);
          print_timings(filestream,&thread_latencies[j]);
          fprintf(filestream,"\n");
       }

    }


    return 0;

}


int mt_do_all_socks_ftp_and_http_and_print(char *prefix, int num_http_threads, int num_ftp_threads){

    pthread_t thread[MAX_NUM_THREADS];
    int i,j;
    int rvalue;
    FILE *filestream=stdout;
    //int num_threads=6;
    int num_hosts=1;
    thread_info_t thread_info[MAX_NUM_THREADS];
    struct timespec delay;

    //make all threads,
    //wait for all threds
    //print all outputs
    if(num_http_threads+num_ftp_threads>MAX_NUM_THREADS){
       return -1;
    }

    for(i=0;i<num_hosts;i++){
       //do prepare
       memset(thread_bw_timings,0x00,sizeof(BW_Timings)*MAX_NUM_THREADS);
       memset(thread_latencies,0x00,sizeof(Latency_Timings)*MAX_NUM_THREADS);
       //make http threads
       for(j=0;j<num_http_threads;j++){
          thread_info[j].host_id=i;
          thread_info[j].thread_id=j;
          //rvalue=pthread_create(&thread[j],NULL,thread_do_socks_ftp,&thread_info[j]);
          rvalue=pthread_create(&thread[j],NULL,thread_do_socks_http,&thread_info[j]);
          if(0!=rvalue){
             perror("cannot create thread"); exit(1);
          }
       }
       //wait 7 seconds
       delay.tv_sec=7;
       delay.tv_nsec=0;
       nanosleep(&delay,NULL);

       //make ftp threads
       for(j=num_http_threads;j<num_ftp_threads+num_http_threads;j++){
          thread_info[j].host_id=i;
          thread_info[j].thread_id=j;
          rvalue=pthread_create(&thread[j],NULL,thread_do_socks_ftp,&thread_info[j]);
          if(0!=rvalue){
             perror("cannot create thread"); exit(1);
          }
       }

       //now join all threads
       for(j=0;j<num_ftp_threads+num_http_threads;j++){
          rvalue=pthread_join(thread[j],NULL);
          if(0!=rvalue){
             perror("cannot join thread"); exit(1);
          }
       }
       //now print the results
       for(j=0;j<num_http_threads;j++){
          fprintf(filestream,"%s thread[%d] ",prefix, j);
          print_bw_timings(filestream,&thread_bw_timings[j]);
          //print_timings(filestream,&thread_latencies[j]);
          fprintf(filestream,"\n");
       }
       for(j=num_http_threads;j<num_ftp_threads+num_http_threads;j++){
          fprintf(filestream,"%s thread[%d] ",prefix,j);
          //print_bw_timings(filestream,&thread_bw_timings[j]);
          print_timings(filestream,&thread_latencies[j]);
          fprintf(filestream,"\n");
       }

    }


    return 0;

}

int alternate_ftp_http(int num_http_threads, int num_ftp_threads){
    char *socks_test_host[] ={
      "localhost",
      "10.4.0.1",
      };
    int socks_test_port[]={9050,1080};
    struct timespec delay;


    socks_port=socks_test_port[1];
    SOCKS_HOST=socks_test_host[1];
    
    mt_do_all_socks_ftp_and_http_and_print("1", num_http_threads,  num_ftp_threads);

    //wait 10 seconds
    delay.tv_sec=10;
    delay.tv_nsec=0;
    nanosleep(&delay,NULL);


    socks_port=socks_test_port[0];
    SOCKS_HOST=socks_test_host[0];
    mt_do_all_socks_ftp_and_http_and_print("0", num_http_threads,  num_ftp_threads);    

};

   


//------------------------------------------------------------------------



//-----------------------------------------------------------------------


int usage(){
   fprintf(stderr, "dont knoww!\n");
}


//--------------------------

int main(int argc, char **argv){
    BW_Timings local1;
    int host_id=1; 
    int numthreads=3;
    char r;
    int do_latency=0;
    int run_option=0;
    SOCKS_HOST=default_host;
    int sleep_interval=300;
    

    while ((r = getopt(argc, argv, "H:n:p:s:hlfa")) != -1){
      switch(r){
        //case 'w': outfilename=optarg; break;
        //case 'e': global_conf.is_exit=1;   break;
        //case 'c': global_conf.is_client=1; break;
        case 's': sleep_interval=atoi(optarg); break;
        case 'p': socks_port=atoi(optarg); break;
        case 'H': SOCKS_HOST=optarg; break;
        case 'n': numthreads=atoi(optarg); break;
        //case 'a': snprintf(global_conf.net_addr,19,"%s",optarg); break;
        case 'l': do_latency=1;run_option|=0x1; break;
        case 'f': run_option|=0x2; break;
        case 'a': run_option|=0x4; break;
        case 'h': usage();exit(1);
        }
    }


    while(1){
      switch(run_option){
         case 0:
            mt_do_all_socks_http_and_print(numthreads);
            break;
         case 0x1:
            mt_do_all_socks_ftp_and_print(numthreads);
            break;
         case 0x2:
            mt_do_all_socks_ftp_and_http_and_print("",numthreads/2, numthreads-numthreads/2);      
            break;
         case 0x4:
            alternate_ftp_http(numthreads/2, numthreads-numthreads/2);
            break;

         default:
            fprintf(stderr,"Invalid run options, aborting\n");
            usage();
            exit(EXIT_FAILURE);
      }
  /*
      if(1==do_latency){
        mt_do_all_socks_ftp_and_print(numthreads);
      }
      else{
        mt_do_all_socks_http_and_print(numthreads);
      }
*/ 
     //do_all_socks_ftp_and_print();
      //do_all_socks_http_and_print();
      sleep(sleep_interval);
    }
/*
    memset(&local1,0x00,sizeof(BW_Timings));
    do_http(http_host[host_id], http_file_path[host_id], &local1);
    print_bw_timings(stderr,&local1);
    fprintf(stderr,"\n");
*/

}


